// Copyright (c) <2010> <Playdead>
// This file is subject to the MIT License as seen in the trunk of this repository
// Maintained by: <Kristian Kjems> <kristian.kjems+UnitySVN@gmail.com>

// This script is a window to display local changes and to perform commands on 
// the repository like updating and committing files.
// SVNIntegration is used to get state and execute commands on the repository.
//
// Although functional the general quality of this file is poor and need a refactor

#define SVN_ENABLED
#if (SVN_ENABLED)
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
using SVNIntegrationNameSpace;
using CommandLineExecution;



public class SVNWindow : EditorWindow
{

    [MenuItem("Window/SVN")]
    static public void Init()
    {
        EditorWindow.GetWindow(typeof(SVNWindow));
    }

    public enum ColumnTypeEnum
    {
        AssetPath = 0,
        Revision,
        FileStatus,
        FileType,
        LockedBy
    }

    private List<CommandLineOutput> svnCommandHistory = new List<CommandLineOutput>();

    Vector2 scrollViewVectorEntries = Vector2.zero;
    Vector2 scrollViewVectorLog = Vector2.zero;
    bool svnBusy = false;
    bool checkForConflicts = false;
    ColumnTypeEnum sortbyColumn = ColumnTypeEnum.AssetPath;
    bool reverseColumnSort = false;
    bool showUnversioned = true;
    bool showMeta = true;
    string commitMessage = "";
    Dictionary<SVNKeyValuePair, bool> selectedAssets = new Dictionary<SVNKeyValuePair, bool>();
    CommandLine currentSVNOperation;

    Dictionary<string, SVNStatus> svnStatusDatabase = new Dictionary<string, SVNStatus>();

    IEnumerable<SVNKeyValuePair> mKeysOfInterrest;
    IEnumerable<SVNKeyValuePair> mFilteredKeysOfInterrest;

    void LoadState()
    {
        sortbyColumn = (ColumnTypeEnum)EditorPrefs.GetInt("SortbyColumn", 0);
        showUnversioned = EditorPrefs.GetBool("ShowUnversioned", true);
        showMeta = EditorPrefs.GetBool("ShowMeta", true);
    }

    void SaveState()
    {
        EditorPrefs.SetInt("SortbyColumn", (int)sortbyColumn);
        EditorPrefs.SetBool("ShowUnversioned", showUnversioned);
        EditorPrefs.SetBool("ShowMeta", showMeta);
    }

    public class SVNKeyValuePair
    {
        public SVNKeyValuePair(string key, SVNStatus svnStatus)
        {
            this.key = key;
            this.svnStatus = svnStatus;
            this.metaFile = null;
        }
        public SVNKeyValuePair(string key, SVNStatus svnStatus, SVNKeyValuePair metaFile)
        {
            this.key = key;
            this.svnStatus = svnStatus;
            this.metaFile = metaFile;
        }
        public readonly string key;
        public readonly SVNStatus svnStatus;
        public readonly SVNKeyValuePair metaFile;
    }

    void UpdateKeysOfInterrest(Dictionary<string, SVNStatus> statusDatabase)
    {
        var svnKeysOfInterrest =
            from keys in statusDatabase.Keys
            let svnStatus = statusDatabase[keys]
            where (svnStatus.fileStatus != ESVNFileStatus.Normal || svnStatus.owner != null)
            orderby svnStatus.fileStatus
            select new SVNKeyValuePair(keys, svnStatus);

        mKeysOfInterrest = svnKeysOfInterrest.ToList();
        UpdateFilteringOfKeys();
    }

    void UpdateFilteringOfKeys()
    {
        mFilteredKeysOfInterrest = new List<SVNKeyValuePair>(mKeysOfInterrest.ToList());
        if (!showUnversioned) mFilteredKeysOfInterrest = mFilteredKeysOfInterrest.Where((k) => svnStatusDatabase[k.key].fileStatus != ESVNFileStatus.Unversioned);
        if (!showMeta) mFilteredKeysOfInterrest = mFilteredKeysOfInterrest.Where((k) => !k.key.EndsWith(".meta"));

        if (sortbyColumn == ColumnTypeEnum.AssetPath) mFilteredKeysOfInterrest = mFilteredKeysOfInterrest.OrderBy((k) => k.key);
        if (sortbyColumn == ColumnTypeEnum.FileStatus) mFilteredKeysOfInterrest = mFilteredKeysOfInterrest.OrderBy((k) => svnStatusDatabase[k.key].fileStatus);
        if (sortbyColumn == ColumnTypeEnum.FileType) mFilteredKeysOfInterrest = mFilteredKeysOfInterrest.OrderBy((k) => GetFileType(k.key));
        if (sortbyColumn == ColumnTypeEnum.Revision) mFilteredKeysOfInterrest = mFilteredKeysOfInterrest.OrderBy((k) => svnStatusDatabase[k.key].revision);
        if (sortbyColumn == ColumnTypeEnum.LockedBy) mFilteredKeysOfInterrest = mFilteredKeysOfInterrest.OrderBy((k) => svnStatusDatabase[k.key].owner);
        if (reverseColumnSort) mFilteredKeysOfInterrest = mFilteredKeysOfInterrest.Reverse();
    }

    bool InterrestingStatus(SVNStatus svnStatus)
    {
        return svnStatus.fileStatus != ESVNFileStatus.Normal || svnStatus.owner != null;
    }

    List<string> GetSelectedAssets()
    {
        return GetSelectedAssets(false);
    }

    List<string> GetSelectedAssets(bool addMetaFiles)
    {
        List<SVNKeyValuePair> assetPaths = new List<SVNKeyValuePair>();
        List<SVNKeyValuePair> toBeRemoved = new List<SVNKeyValuePair>();
        foreach (KeyValuePair<SVNKeyValuePair, bool> selectedAssetIt in selectedAssets)
        {
            if (!mFilteredKeysOfInterrest.Contains(selectedAssetIt.Key))
            {
                toBeRemoved.Add(selectedAssetIt.Key);
                continue;
            }
            if (selectedAssetIt.Value)
            {
                assetPaths.Add(selectedAssetIt.Key);
                if (addMetaFiles)
                {
                    if (selectedAssetIt.Key.metaFile != null && selectedAssetIt.Key.metaFile.svnStatus.fileStatus != ESVNFileStatus.Normal)
                    {
                        assetPaths.Add(selectedAssetIt.Key.metaFile);
                    }
                }
            }
        }
        foreach (var keyIt in toBeRemoved)
        {
            selectedAssets.Remove(keyIt);
        }
        return assetPaths.Select((kv) => kv.key).ToList();
    }

    List<string> GetDependencies(List<string> assetPaths)
    {
        List<string> dependencies = new List<string>();
        foreach (string assetPathIt in assetPaths)
        {
            Object objAtAssetPath = AssetDatabase.LoadAssetAtPath(assetPathIt, typeof(Object));
            foreach (Object dependencyIt in EditorUtility.CollectDependencies(new Object[] { objAtAssetPath }))
            {
                dependencies.Add(AssetDatabase.GetAssetPath(dependencyIt));
                dependencies.Add(AssetDatabase.GetAssetPath(dependencyIt) + ".meta");
            }
        }
        dependencies = dependencies
            .Distinct()
            .Where((dep) => !assetPaths.Contains(dep))
            .Where((dep) => SVNCommands.instance.GetAssetStatus(dep).fileStatus != ESVNFileStatus.Normal)
            .ToList();

        return dependencies;
    }

    void OnEnable()
    {
        SVNIntegration.instance.SVNDatabaseUpdated += new SVNIntegration.SVNDatabaseHandler(RefreshGUI);
        SVNCommands.instance.UpdateSVNStatus();
        LoadState();
    }

    void OnDisable()
    {
        SVNIntegration.instance.SVNDatabaseUpdated -= new SVNIntegration.SVNDatabaseHandler(RefreshGUI);
        SaveState();
    }

    private bool ImportantSVNError(CommandLineExecution.CommandLineOutput output)
    {
        if (output.errorStr.Contains("svn: warning:"))
        {
            return false;
        }
        if (output.errorStr.Contains("is not locked in this working copy"))
        {
            return false;
        }
        if (output.errorStr.Contains("is not under version control"))
        {
            return false;
        }
        return true;
    }

    void RefreshGUI(object source, SVNDatabaseArgs e)
    {
        if (e is SVNDatabaseUpdatedArgs)
        {
            SVNDatabaseUpdatedArgs args = e as SVNDatabaseUpdatedArgs;
            svnBusy = false;
            svnStatusDatabase = args.statusDatabase;
            UpdateKeysOfInterrest(args.statusDatabase);
            svnCommandHistory.Add(args.output);
            Repaint();
            checkForConflicts = true;
            SaveState();
            currentSVNOperation = null;
        }
        if (e is SVNDatabaseBusyArgs)
        {
            SVNDatabaseBusyArgs args = e as SVNDatabaseBusyArgs;
            currentSVNOperation = args.commandlineOperation.command;
            svnBusy = true;
            Repaint();
        }
        if (e is SVNDatabaseErrorArgs)
        {
            SVNDatabaseErrorArgs args = e as SVNDatabaseErrorArgs;

            CommandLineOutput output = args.output;
            string userMessage = output.command + " " + output.arguments + "\n\n" + output.errorStr + "\nExitCode:" + output.exitcode;
            if (output.exception != null) userMessage += "\nException: \n" + output.exception.Message;
            if (ImportantSVNError(output))
            {
                Debug.LogWarning(userMessage);
                EditorUtility.DisplayDialog("SVN Warning", userMessage, "OK");
            }
            else
            {
                Debug.Log("Verbose SVN message:\n" + userMessage);
            }

        }
    }

    void HandleConflicts()
    {
        var svnKeysWithConflict = (
            from keys in svnStatusDatabase.Keys
            where svnStatusDatabase[keys].fileStatus == ESVNFileStatus.Conflicted
            select keys).ToList();

        foreach (var svnStatusKeyIt in svnKeysWithConflict)
        {
            bool theirs = EditorUtility.DisplayDialog("SVN Conflict", "There is a conflict in the file '" + svnStatusKeyIt + "'. Use 'Theirs' or 'Mine'?", "Theirs", "Mine");
            SVNCommands.instance.SVNResolve(new string[] { svnStatusKeyIt }, false, theirs);
        }
        if (svnKeysWithConflict.Count > 0)
        {
            AssetDatabase.Refresh();
            SVNCommands.instance.UpdateSVNStatusSync();
        }
    }

    void Update()
    {
        if (checkForConflicts && !svnBusy)
        {
            HandleConflicts();
            checkForConflicts = false;
        }
    }

    string GetFileType(string assetPath)
    {
        int indexOfLastDot = assetPath.LastIndexOf(".");
        return (indexOfLastDot > 0) ? assetPath.Substring(assetPath.LastIndexOf(".") + 1) : "[folder]";
    }

    Color GetStatusColorCode(SVNStatus svnStatus)
    {

        if (svnStatus.fileStatus == ESVNFileStatus.Unversioned) return Color.grey;
        if (svnStatus.fileStatus == ESVNFileStatus.Normal) return Color.black;
        if (svnStatus.fileStatus == ESVNFileStatus.Missing) return new Color(0.6f, 0.0f, 0.2f);
        if (svnStatus.fileStatus == ESVNFileStatus.Conflicted) return Color.red;
        if (svnStatus.fileStatus == ESVNFileStatus.Modified) return Color.blue;
        return Color.black;
    }


    void OnGUI()
    {
        EditorGUILayout.BeginVertical();
        GUILayoutOption[] buttonLayout = { GUILayout.MaxWidth(50) };
        {// Buttons at top        
            EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
            if (!svnBusy)
            {
                if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, buttonLayout) || Event.current.keyCode == KeyCode.F5)
                {
                    List<string> assetsToBeCommited = GetSelectedAssets();
                    List<string> dependencies = GetDependencies(assetsToBeCommited);
                    foreach (string depIt in dependencies)
                    {
                        Debug.Log(depIt);
                    }
                    EditorApplication.SaveAssets();
                    SVNCommands.instance.UpdateSVNStatus();
                }
                if (GUILayout.Button("Update", EditorStyles.toolbarButton, buttonLayout))
                {                
                    SVNCommands.instance.SVNUpdate();
                }
                if (GUILayout.Button("Revert", EditorStyles.toolbarButton, buttonLayout))
                {
                    EditorApplication.SaveAssets();
                    SVNCommands.instance.SVNRevert(GetSelectedAssets().ToArray());
                }
                if (GUILayout.Button("Delete", EditorStyles.toolbarButton, buttonLayout))
                {
                    SVNCommands.instance.SVNDelete(GetSelectedAssets().ToArray());
                }
                if (GUILayout.Button("Unlock", EditorStyles.toolbarButton, buttonLayout))
                {
                    SVNCommands.instance.SVNReleaseLock(GetSelectedAssets().ToArray());
                }
                if (GUILayout.Button("Commit", EditorStyles.toolbarButton, buttonLayout))
                {
                    EditorApplication.SaveCurrentSceneIfUserWantsTo();
                    List<string> assetsToBeCommited = GetSelectedAssets();
                    List<string> dependencies = GetDependencies(assetsToBeCommited);
                    if (dependencies.Count > 0)
                    {
                        string depList = dependencies.Aggregate((i, j) => i + "\n" + j);
                        if (EditorUtility.DisplayDialog("Add Dependencies?", "Add following dependencies?\n\n" + depList, "Yes", "No"))
                        {
                            assetsToBeCommited.AddRange(dependencies);
                        }
                    }

                    var bypassAssets = assetsToBeCommited.Where((asset) => SVNCommands.instance.GetAssetStatus(asset).bypassRevisionControl).ToArray();
                    if (bypassAssets.Length > 0)
                    {
                        string bypassList = bypassAssets.Aggregate((i, j) => i + "\n" + j);
                        if (EditorUtility.DisplayDialog("Get Lock?", "Try and get lock for following unlocked assets?\n\n" + bypassList, "Yes", "No"))
                        {
                            SVNCommands.instance.SVNGetLock(bypassAssets.ToArray(), false);
                        }
                    }

                    EditorApplication.SaveAssets();
                    SVNCommands.instance.SVNCommit(assetsToBeCommited.ToArray(), commitMessage.Length == 0 ? "Commit from Unity" : commitMessage);
                }

            }
            else
            {
                GUILayout.Label("Busy...");
            }
            commitMessage = GUILayout.TextField(commitMessage, EditorStyles.toolbarTextField, GUILayout.MinWidth(100), GUILayout.ExpandWidth(true));
            bool newShowUnversioned = GUILayout.Toggle(showUnversioned, "Unversioned", EditorStyles.toolbarButton, new GUILayoutOption[] { GUILayout.MaxWidth(80) });
            bool newShowMeta = GUILayout.Toggle(showMeta, "Meta", EditorStyles.toolbarButton, buttonLayout);

            if (newShowUnversioned != showUnversioned)
            {
                showUnversioned = newShowUnversioned;
                UpdateFilteringOfKeys();
            }

            if (newShowMeta != showMeta)
            {
                showMeta = newShowMeta;
                UpdateFilteringOfKeys();
            }

            EditorGUILayout.EndHorizontal();
            EditorGUILayout.Separator();
        }

        {// SVN elements on scrollable list                  

            int columnSpacing = 20;

            GUILayoutOption[] propertyLayout = { GUILayout.MaxWidth(75), GUILayout.Width(75) };
            GUILayoutOption[] assetPathLayout = { GUILayout.MaxWidth(400), GUILayout.Width(400) };

            GUIStyle headingStyle = new GUIStyle(GUI.skin.label);
            GUIStyle labelStyle = new GUIStyle(GUI.skin.label);
            //labelStyle.normal.textColor = GUI.skin.label.normal new Color(0.2f, 0.2f, 0.2f);

            GUIStyle selectedLabelStyle = new GUIStyle(labelStyle);
            //selectedLabelStyle.normal.textColor = new Color(0.0f, 0.0f, 0.0f);

            { // Header 
                EditorGUILayout.BeginHorizontal();
                headingStyle.fontStyle = FontStyle.Bold;
                if (GUILayout.Button("\tAsset Path", headingStyle, assetPathLayout))
                {
                    if (sortbyColumn == ColumnTypeEnum.AssetPath) reverseColumnSort = !reverseColumnSort;
                    else reverseColumnSort = false;
                    sortbyColumn = ColumnTypeEnum.AssetPath;
                    UpdateFilteringOfKeys();
                }
                GUILayout.Space(columnSpacing);

                //(reverseColumnSort ? "△" : "▽")
                if (GUILayout.Button("File Type", headingStyle, propertyLayout))
                {
                    if (sortbyColumn == ColumnTypeEnum.FileType) reverseColumnSort = !reverseColumnSort;
                    else reverseColumnSort = false;
                    sortbyColumn = ColumnTypeEnum.FileType;
                    UpdateFilteringOfKeys();
                }
                GUILayout.Space(columnSpacing);


                if (GUILayout.Button("File Status", headingStyle, propertyLayout))
                {
                    if (sortbyColumn == ColumnTypeEnum.FileStatus) reverseColumnSort = !reverseColumnSort;
                    else reverseColumnSort = false;
                    sortbyColumn = ColumnTypeEnum.FileStatus;
                    UpdateFilteringOfKeys();
                }
                GUILayout.Space(columnSpacing);


                if (GUILayout.Button("Locked By", headingStyle, propertyLayout))
                {
                    if (sortbyColumn == ColumnTypeEnum.LockedBy) reverseColumnSort = !reverseColumnSort;
                    else reverseColumnSort = false;
                    sortbyColumn = ColumnTypeEnum.LockedBy;
                    UpdateFilteringOfKeys();
                }

                headingStyle.fontStyle = FontStyle.Normal;
                EditorGUILayout.EndHorizontal();
                EditorGUILayout.Separator();

            }
            scrollViewVectorEntries = EditorGUILayout.BeginScrollView(scrollViewVectorEntries, false, false);
            int numberOfEntries = 0;

            // List content
            if (mFilteredKeysOfInterrest != null && mFilteredKeysOfInterrest.Count() != 0)
            {
                foreach (SVNKeyValuePair svnStatusKeyIt in mFilteredKeysOfInterrest)
                {
                    if (!selectedAssets.ContainsKey(svnStatusKeyIt)) selectedAssets[svnStatusKeyIt] = false;
                    SVNStatus svnStatusValueIt = svnStatusKeyIt.svnStatus;
                    numberOfEntries++;
                    EditorGUILayout.BeginHorizontal();
                    Color previousColor = labelStyle.normal.textColor;

                    // AssetPath •
                    if (svnStatusValueIt.treeConflictStatus == ESVNTreeConflictStatus.TreeConflict) labelStyle.normal.textColor = Color.red;
                    if (GUILayout.Button((selectedAssets[svnStatusKeyIt] ? "+\t" : "\t") + svnStatusKeyIt.key, (selectedAssets[svnStatusKeyIt] ? selectedLabelStyle : labelStyle), assetPathLayout)) selectedAssets[svnStatusKeyIt] = !selectedAssets[svnStatusKeyIt];
                    GUILayout.Space(columnSpacing);

                    // FileType                
                    GUILayout.Label(GetFileType(svnStatusKeyIt.key), labelStyle, propertyLayout); GUILayout.Space(columnSpacing);
                    labelStyle.normal.textColor = GetStatusColorCode(svnStatusValueIt);

                    // FileStatus
                    GUILayout.Label(svnStatusValueIt.fileStatus.ToString() + (svnStatusValueIt.bypassRevisionControl ? "*" : ""), labelStyle, propertyLayout); GUILayout.Space(columnSpacing);
                    labelStyle.normal.textColor = previousColor;

                    // LockedBy
                    GUILayout.Label(svnStatusValueIt.owner, labelStyle, propertyLayout);

                    EditorGUILayout.EndHorizontal();
                }
            }

            EditorGUILayout.EndScrollView();
        }

        { // SVN message box                                
            //Debug.Log(messageHistory.Count);
            string allEntries = "";
            if (currentSVNOperation != null)
            {
                allEntries = "Currently executing following command:\n" + currentSVNOperation.ToString();
            }
            else
            {
                for (int i = svnCommandHistory.Count - 1; i >= 0; i--)
                {
                    if (!svnCommandHistory[i].arguments.Contains("status --non-interactive --xml"))
                        allEntries += svnCommandHistory[i].command + " " + svnCommandHistory[i].arguments + ":\n" + svnCommandHistory[i].outputStr + "-----------------------\n";
                }
            }
            EditorGUILayout.BeginVertical(GUILayout.Height(135));
            EditorGUILayout.BeginHorizontal();
            GUILayout.Label(new GUIContent("SVN Output"));
            EditorGUILayout.EndHorizontal();
            scrollViewVectorLog = EditorGUILayout.BeginScrollView(scrollViewVectorLog, false, false);
            float heightOfLogInPixles = Mathf.Max(100.0f, GUI.skin.window.CalcSize(new GUIContent(allEntries)).y);

            GUI.skin.box.alignment = TextAnchor.UpperLeft;
            GUILayout.Box(allEntries, GUILayout.MinHeight(heightOfLogInPixles), GUILayout.ExpandWidth(true));
            EditorGUILayout.EndScrollView();
            EditorGUILayout.EndVertical();
        }

        EditorGUILayout.EndVertical();

    }

}
#endif
